﻿//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////

using Glodon.Lookup.ViewModels.Objects;
using Glodon.Lookup.Views.Utils;

using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace Glodon.Lookup.Views.Controls
{
    public class SnoopPath : ItemsControl
    {
        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            var source = ItemsSource as ObservableCollection<SnoopAction>;
            source!.CollectionChanged += Source_CollectionChanged;
        }

        // 数据源变化时滚动 SnoopPath 内容到最右端, 以避免最新历史节点不可见
        private void Source_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems != null)
            {
                VisualUtils.FindVisualChild<ScrollViewer>(this).ScrollToRightEnd();
            }
        }

        /// <summary>
        /// 获取目标控件的宽度范围是否已经初始化
        /// </summary>
        /// <param name="target"></param>
        /// <returns></returns>
        public static bool GetIsWidthInitialized(DependencyObject target)
        {
            return (bool)target.GetValue(IsWidthInitializedProperty);
        }

        /// <summary>
        /// 设置目标控件的宽度范围是否已经初始化
        /// </summary>
        /// <param name="target"></param>
        /// <param name="value"></param>
        public static void SetIsWidthInitialized(DependencyObject target, bool value)
        {
            target.SetValue(IsWidthInitializedProperty, value);
        }

        // Using a DependencyProperty as the backing store for IsWidthInitialized.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsWidthInitializedProperty =
            DependencyProperty.Register("IsWidthInitialized", typeof(bool), typeof(SnoopPath), new PropertyMetadata(false));


        public double MinItemWidth
        {
            get { return (double)GetValue(MinItemWidthProperty); }
            set { SetValue(MinItemWidthProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MinItemWidthProperty =
            DependencyProperty.Register("MinItemWidth", typeof(double), typeof(SnoopPath), new PropertyMetadata(20.0));

        /// <summary>
        /// 设置当SnoopPath剩余空间改变时，是否重新调整每个元素尺寸：
        /// 当剩余空间变少时缩小所有可缩小元素，以尽可能显示全部元素；
        /// 当剩余空间变多时拉伸所有可拉伸元素，以尽可能占满剩余空间。
        ///   可缩小：实际尺寸大于最小尺寸；
        ///   可拉伸：实际尺寸小于最大尺寸
        /// </summary>
        public bool CanResizeItems
        {
            get { return (bool)GetValue(CanResizeItemsProperty); }
            set { SetValue(CanResizeItemsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CanResizeItemsProperty =
            DependencyProperty.Register("CanResizeItems", typeof(bool), typeof(SnoopPath), new UIPropertyMetadata(OnCanResizeItemsChanged));

        /// <summary>
        /// 当 CanResizeItems 设为 true 时，SnoopPath尺寸改变时会自动调整元素尺寸
        /// </summary>
        /// <param name="d"></param>
        /// <param name="e"></param>
        private static void OnCanResizeItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if ((bool)e.NewValue)
            {
                if (d is SnoopPath snoopPath)
                {
                    snoopPath.SizeChanged += (s, e) =>
                    {
                        snoopPath.ResizeItems();
                        VisualUtils.FindVisualChild<ScrollViewer>(snoopPath).ScrollToRightEnd();
                    };
                }
            }
        }
        /// <summary>
        /// 根据容器内的TextBlock的文字渲染宽度设定容器的最大最小宽度
        /// </summary>
        /// <param name="textPanel"></param>
        private void SetItemTextPanelWidthRange(Panel itemPanel)
        {
            if (itemPanel != null)
            {
                //首次设置Border宽度时，TextBlock 的实际宽度等于文字渲染宽度
                var textBorder = VisualUtils.FindVisualChild<Border>(itemPanel);
                textBorder.MinWidth = Math.Min(MinItemWidth, textBorder.ActualWidth);
                textBorder.MaxWidth = textBorder.ActualWidth;
                SetIsWidthInitialized(textBorder, true);
                var textBlock = VisualUtils.FindVisualChild<TextBlock>(textBorder);
                //文字改变时根据新的文字渲染宽度重新设置 Border 宽度范围
                //(因为设置Border宽度后，TextBlock的实际宽度跟随Border，不再跟随其文字)
                textBlock.TargetUpdated += (sender, args) =>
                {
                    if (args.Property.Name.Equals(nameof(TextBlock.Text)))
                    {
                        double dpiScale = VisualTreeHelper.GetDpi(this).PixelsPerDip;
                        FormattedText formattedText = new FormattedText(
                            textBlock.Text,
                            System.Globalization.CultureInfo.CurrentCulture,
                            FlowDirection.LeftToRight,
                            new Typeface(textBlock.FontFamily, textBlock.FontStyle, textBlock.FontWeight, textBlock.FontStretch),
                            textBlock.FontSize,
                            textBlock.Foreground, dpiScale);

                        textBorder.MinWidth = Math.Min(MinItemWidth, formattedText.Width + textBlock.Padding.Left + textBlock.Padding.Right);
                        textBorder.MaxWidth = formattedText.Width + textBlock.Padding.Left + textBlock.Padding.Right;
                    }
                };
            }
        }

        /// <summary>
        /// 每当有新元素加载完成时，SnoopPath 剩余空间发生改变，需要重新调整元素尺寸
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void OnItemPanelLoaded(object sender, RoutedEventArgs e)
        {
            var panel = sender as Panel;
            if (panel != null && CanResizeItems)
            {
                SetItemTextPanelWidthRange(panel);
                ResizeItems();
            }
        }



        /// <summary>
        /// 尝试缩每个Item中Border宽度，以使当前 SnoopPath 内容 宽度不超过 SnooPath 控件 宽度
        /// </summary>
        private void ResizeItems()
        {
            Panel itemsPanel = null;
            var firstItemContainer = ItemContainerGenerator.ContainerFromIndex(0);
            if (firstItemContainer != null)
            {
                itemsPanel = VisualTreeHelper.GetParent(firstItemContainer) as Panel;
            }
            if (itemsPanel == null) return;
            var allItemsContent = VisualUtils.FindAllVisualChildren<ContentPresenter>(itemsPanel, false);
            double allChildrenWidth = 0;
            foreach (var contentPresenter in allItemsContent)
            {
                allChildrenWidth += contentPresenter.ActualWidth;
            }
            double widthNeedResizeTotal = ActualWidth - allChildrenWidth;
            var itemsCanResize = new List<FrameworkElement>();
            double widthCanResizeTotal = 0;
            //遍历Items, 找到所有位于Item中并且可以调整宽度的Border
            foreach (var item in Items)
            {
                if (ItemContainerGenerator.ContainerFromItem(item) is FrameworkElement container)
                {
                    var textBorder = VisualUtils.FindVisualChild<Border>(container);
                    if (textBorder != null && GetIsWidthInitialized(textBorder))
                    {

                        bool canSqueeze = textBorder.MinWidth < textBorder.ActualWidth && widthNeedResizeTotal < 0;
                        bool canExpand = textBorder.MaxWidth > textBorder.ActualWidth && widthNeedResizeTotal > 0;
                        if (canSqueeze || canExpand)
                        {
                            itemsCanResize.Add(textBorder);
                            if (canSqueeze)
                            {
                                widthCanResizeTotal += textBorder.ActualWidth - textBorder.MinWidth;
                            }
                            else
                            {
                                widthCanResizeTotal += textBorder.MaxWidth - textBorder.ActualWidth;
                            }
                        }
                    }
                }
            }
            //对可以调整宽度的Border，逐个调整宽度
            foreach (var itemControl in itemsCanResize)
            {
                double widthCanResize;
                if (widthNeedResizeTotal < 0)
                {
                    widthCanResize = itemControl.MinWidth - itemControl.ActualWidth;
                }
                else
                {
                    widthCanResize = itemControl.MaxWidth - itemControl.ActualWidth;
                }
                double widthCanResizeTotalAbs = Math.Abs(widthCanResizeTotal);
                double widthNeedResizeTotalAbs = Math.Abs(widthNeedResizeTotal);
                //当总所需调整值大于总可调整值时，当前元素需要调整的值==当前元素可调整的值
                //否则，当前元素需要调整的值 == 当前元素的可调整值 * 总需要调整的值/总可调整值
                double widthNeedResize = widthCanResize * Math.Abs(widthNeedResizeTotal)
                                        / Math.Max(widthNeedResizeTotalAbs, widthCanResizeTotalAbs);
                itemControl.Width = itemControl.ActualWidth + widthNeedResize;
            }
        }
    }
}
